
/*
  =======================================================================================
  File: 
        dt_btn_util.c

  Description: 
        The implementation for phisical button utility functions.

  Author:
       Delphi Tang (唐佐林)
       http://www.dt4sw.com/

  Revision History:
       V0.0.1 (Initial Version)
       V0.0.2 ( 
                1. Do refactory for the interface to encapsulate the vendor SDK details
                2. Move event handler call to a special thread to avoid heavy process in ISR
                3. Trigger long-pressed-event after pressed-event for about 2.5 seconds 
              )
  =======================================================================================
*/

#include <unistd.h>
#include <string.h>
#include "cmsis_os2.h"
#include "hi_systick.h"
#include "wifiiot_gpio.h"
#include "wifiiot_gpio_ex.h"
#include "dt_btn_util.h"

#define BUTTON_STACK_SIZE   2048
#define PRESS_INTERVAL      20000
#define LOOP_INTERVAL       40000
#define LONG_PRESS_INTERVAL 64
#define LONG_PRESS_END      0xFFFF
#define INDEX_ERR           -1

typedef struct
{
    const char* name;
    unsigned int gpio;
    unsigned int event;
    PBtnCallback callback;
} ButtonInfo;

typedef struct
{
    int pressClicked;
    int longPressClicked;
    int longPressInterval;
    int releaseClicked;
} ButtonClicked;

static volatile hi_u64 gHisTick = 0;
static volatile int gToClick = 0;
static volatile int gIsInit = 0;

static const char* gBtnName[] = {
    "GPIO_0",  "GPIO_1",  "GPIO_2",  "GPIO_3",  "GPIO_4",
    "GPIO_5",  "GPIO_6",  "GPIO_7",  "GPIO_8",  "GPIO_9",
    "GPIO_10", "GPIO_11", "GPIO_12", "GPIO_13", "GPIO_14",
    NULL
};

static volatile ButtonInfo gBtnInfo[WIFI_IOT_IO_NAME_MAX] = {0};
static volatile ButtonClicked gBtnClicked[WIFI_IOT_IO_NAME_MAX] = {0};

static void OnButtonPressed(char* arg);
static void OnButtonReleased(char* arg);

static int GetGpioIndex(const char* name)
{
    int ret = INDEX_ERR;
    int i = 0;

    while( gBtnName[i] && name )
    {
        if( strcmp(gBtnName[i], name) == 0 )
        {
            ret = i;
            break;
        }

        i++;
    }

    return ret;
}

static void OnButtonPressed(char* arg)
{
    WifiIotIoName gpio = (WifiIotIoName)arg;
    hi_u64 tick = hi_systick_get_cur_tick();
    
    gToClick = (tick - gHisTick) > PRESS_INTERVAL;
    
    if( gToClick )
    {
        gHisTick = tick;

        gBtnClicked[gpio].pressClicked = 1;

        GpioRegisterIsrFunc(gpio,
                            WIFI_IOT_INT_TYPE_EDGE, 
                            WIFI_IOT_GPIO_EDGE_RISE_LEVEL_HIGH,
                            OnButtonReleased, arg);
    }          
}

static void OnButtonReleased(char* arg)
{
    WifiIotIoName gpio = (WifiIotIoName)arg;
    
    if( gToClick )
    {
        gBtnClicked[gpio].releaseClicked = 1;

        GpioRegisterIsrFunc(gpio,
                            WIFI_IOT_INT_TYPE_EDGE, 
                            WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW,
                            OnButtonPressed, arg);
    }
}

static void CommonEventHandler(void)
{
    int i = 0;

    for(i=0; i<WIFI_IOT_IO_NAME_MAX; i++)
    {
        const char* name = gBtnInfo[i].name;

        if( (gBtnInfo[i].event & Pressed) && gBtnClicked[i].pressClicked )
        {
            gBtnInfo[i].callback(name, Pressed);
            gBtnClicked[i].pressClicked = 0;
            gBtnClicked[i].longPressInterval = 0;
        }

        if( gBtnClicked[i].longPressInterval < LONG_PRESS_END )
        {
            gBtnClicked[i].longPressInterval++;
        }

        if( gBtnClicked[i].longPressInterval == LONG_PRESS_INTERVAL )
        {
            gBtnClicked[i].longPressClicked = 1;
        }

        if( (gBtnInfo[i].event & LongPressed) && gBtnClicked[i].longPressClicked )
        {
            gBtnInfo[i].callback(name, LongPressed);
            gBtnClicked[i].longPressClicked = 0;
            gBtnClicked[i].longPressInterval = LONG_PRESS_END;
        }

        if( (gBtnInfo[i].event & Released) && gBtnClicked[i].releaseClicked )
        {
            gBtnInfo[i].callback(name, Released);
            gBtnClicked[i].releaseClicked = 0;
            gBtnClicked[i].longPressInterval= LONG_PRESS_END;
        }
    }
}

static void* DTButton_Task(const char* arg)
{
    (void)arg;

    while( gIsInit )
    {
        CommonEventHandler();

        usleep(LOOP_INTERVAL);
    }
    
    return NULL;
}

int DTButton_Init(void)
{
    int ret = (int)GpioInit();

    if( ret == (int)HI_ERR_GPIO_REPEAT_INIT )
    {
        ret = 0;
    }

    if( !ret && !gIsInit )
    {
        int i = 0;
        osThreadAttr_t attr = {0};

        for(i=0; i<WIFI_IOT_IO_NAME_MAX; i++)
        {
            gBtnClicked[i].longPressInterval = LONG_PRESS_END;
        }

        attr.name = "DTButton_Task";
        attr.attr_bits = 0U;
        attr.cb_mem = NULL;
        attr.cb_size = 0U;
        attr.stack_mem = NULL;
        attr.stack_size = BUTTON_STACK_SIZE;
        attr.priority = osPriorityNormal;

        ret += (osThreadNew((osThreadFunc_t)DTButton_Task, NULL, &attr) == NULL);

        gIsInit = (ret == 0);
    }

    return ret;
}

void DTButton_Deinit(void)
{
    gIsInit = 0;
}

int DTButton_Enable(const char* name, PBtnCallback callback, unsigned int event)
{
    int ret = -1;
    int gpio = name ? GetGpioIndex(name) : INDEX_ERR;

    if( (WIFI_IOT_IO_NAME_GPIO_0 <= gpio) && (gpio < WIFI_IOT_IO_NAME_MAX) && callback && event)
    {   
        ret  = IoSetFunc((WifiIotIoName)gpio, 0);
        ret += GpioSetDir((WifiIotIoName)gpio, WIFI_IOT_GPIO_DIR_IN);
        ret += IoSetPull((WifiIotIoName)gpio, WIFI_IOT_IO_PULL_UP);
        ret += GpioRegisterIsrFunc((WifiIotIoName)gpio,
                            WIFI_IOT_INT_TYPE_EDGE, 
                            WIFI_IOT_GPIO_EDGE_FALL_LEVEL_LOW,
                            OnButtonPressed, (char*)gpio);                   
    }

    if( ret == 0 )
    {
        gBtnInfo[gpio].name = name;
        gBtnInfo[gpio].gpio = gpio;
        gBtnInfo[gpio].event = event;
        gBtnInfo[gpio].callback = callback;
    }

    return ret;
}

void DTButton_Disable(const char* name)
{
    int gpio = name ? GetGpioIndex(name) : INDEX_ERR;
    
    if( gpio != INDEX_ERR )
    {
        gBtnInfo[gpio].name = 0;
        gBtnInfo[gpio].gpio = 0;
        gBtnInfo[gpio].event = 0;
        gBtnInfo[gpio].callback = 0;
    }
}